package com.xunfang.portal.controller;

import cn.hutool.core.util.RandomUtil;
import com.xunfang.common.RedisConst;
import com.xunfang.common.enumeration.UserStatus;
import com.xunfang.common.enumeration.impl.BusinessCode;
import com.xunfang.common.exception.Assert;
import com.xunfang.portal.model.entity.User;
import com.xunfang.portal.model.request.LoginRequest;
import com.xunfang.portal.model.request.RegisterRequest;
import com.xunfang.portal.model.vo.UserVo;
import com.xunfang.portal.service.UserService;
import com.xunfang.portal.utils.JwtUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author yellow Doctor
 * @date 2025-10-13
 * @desc https://blog.csdn.net/hekai7217
 */
@RestController
@RequestMapping("/user")
@Tag(name="用户管理模块")
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private JwtUtils jwtUtils;

    /**
     * 用户名和密码，验证码登录
     */
    @PostMapping("/login")
    @Operation(summary = "用户登录")
    public ResponseEntity<?> login(@RequestBody LoginRequest param, HttpServletRequest request) {

        //1.调用业务层进行登录
        ResponseEntity<?> responseEntity = userService.login(param);
        //2.如果登录成功则创建Token
        if (responseEntity.getStatusCode() == HttpStatus.OK) {
            //得到用户对象
            User user = (User) responseEntity.getBody();
            //登录成功，创建Token
            return createTokenAndUserVo(request, user);
        } else {
            //登录失败，返回错误信息
            return responseEntity;
        }
    }

    /**
     *  更加用户信息 会生成一个token
     * @param request
     * @param user
     * @return
     */
    private ResponseEntity<?> createTokenAndUserVo(HttpServletRequest request, User user) {

        //token 会劫持

        //1.生成Token
        HashMap<String, Object> payLoad = new HashMap<>();
        payLoad.put("userId", user.getId());
        payLoad.put("userName", user.getAccount());
        //为了安全，判断IP地址是否相同
        payLoad.put("ip", request.getRemoteAddr());
        String token = jwtUtils.createToken(payLoad);
        log.info("用户ID：{} 登录成功，生成的Token为：{}", user.getId(), token);

        //2.存储到Redis中，保存1天
        String key = RedisConst.USER_TOKEN_PREFIX + ":" + user.getId();
        stringRedisTemplate.opsForValue().set(key, token, Duration.ofDays(1));

        //3.封装UserVo对象
        UserVo userVo = new UserVo();
        // 数据库中的user
        // 前端页面的userVo
        //user ---》 userVo
//        userVo.setState(user.getState());
//        userVo.setId(user.getId());
//        userVo.setRealName(user.getRealName());

        //hutool 中 有工具类  BeanUtils 可以把一个对象中相同的属性复制给另外一个对象中  copyProperties
        //复制属性：源对象，目标对象
        BeanUtils.copyProperties(user, userVo);

        //4.以响应头的方式发送Token回去
        return ResponseEntity.ok()
                .header("Authorization", token)
                //告知浏览器可以访问 Authorization 响应头，否则访问不了
                .header("Access-Control-Expose-Headers", "Authorization")
                .body(userVo);

    }

    /**
     * 判断用户是否登录
     * 数据从请求头中获取Token数据
     */
    @GetMapping("/isLogged")
    @Operation(summary = "判断用户是否登录")
    public ResponseEntity<?> isLogged(@RequestHeader(required = false, name = "Authorization") String authorization) {
        log.info("用户请求头中携带的Token是：{}", authorization);
        //如果为空表示没有登录
        Assert.error(StringUtils.isEmpty(authorization), BusinessCode.USER_NOT_LOGGED);
        //判断Token是否有效
        boolean isValid = jwtUtils.validateToken(authorization);
        Assert.error(!isValid, BusinessCode.INVALID_TOKEN);
        //获取用户的ID
        Long userId = Long.valueOf(jwtUtils.getValue(authorization, "userId"));
        //调用业务层查询用户是否存在
        UserVo user = userService.findById(userId);
        Assert.error(user == null, BusinessCode.USER_NOT_EXIST);
        return ResponseEntity.ok(user);
    }


    /**
     * 退出  删除redis 中的token
     */
    @DeleteMapping("/logout")
    @Operation(summary = "退出")
    public ResponseEntity<String> logout(@RequestHeader("Authorization") String token, HttpServletResponse response) {
        //通过token得到用户id
        String userId = jwtUtils.getValue(token, "userId");
        //删除redis中的token
        stringRedisTemplate.delete(RedisConst.USER_TOKEN_PREFIX + ":" + userId);
        //清除缓存
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Expires", "0");
        return ResponseEntity.ok("退出成功");
    }


    /**
     * 图片验证码
     */
    @GetMapping("/code")
    @Operation(summary = "生成美化版图片验证码")
    public ResponseEntity generateCaptcha() throws IOException {
        int width = 75, height = 45;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();

        // === 1. 启用抗锯齿 ===
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        // === 2. 渐变背景 ===
        GradientPaint gradient = new GradientPaint(0, 0, new Color(240, 240, 255), width, height, Color.WHITE);
        g2d.setPaint(gradient);
        g2d.fillRect(0, 0, width, height);

        // === 3. 验证码字符生成 ===
        StringBuilder sb = new StringBuilder();
        String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        Random random = new Random();

        for (int i = 0; i < 2; i++) {
            char c = characters.charAt(random.nextInt(characters.length()));
            sb.append(c);

            // 随机字体大小
            int fontSize = 24 + random.nextInt(10);
            g2d.setFont(new Font("Arial", Font.BOLD | Font.ITALIC, fontSize));
            g2d.setColor(getColor());

            // 随机 y 坐标偏移
            int y = 25 + random.nextInt(12);

            // 随机轻微旋转
            double angle = (random.nextDouble() - 0.5) * 0.3; // -0.15 ~ +0.15 弧度
            g2d.rotate(angle, 5 + (i * 23) + 10, y);
            g2d.drawString(String.valueOf(c), 5 + (i * 23), y);
            g2d.rotate(-angle, 5 + (i * 23) + 10, y);
        }

        // === 4. 干扰元素：噪点 + 小圆点 ===
        g2d.setColor(getColor());
        for (int i = 0; i < 40; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int radius = random.nextInt(2) + 1;
            g2d.fillOval(x, y, radius, radius);
        }

        // === 5. 贝塞尔干扰曲线（可选）===
        for (int i = 0; i < 2; i++) {
            g2d.setColor(getColor());
            int x1 = 0;
            int y1 = random.nextInt(height);
            int x2 = width;
            int y2 = random.nextInt(height);
            int ctrlx = random.nextInt(width);
            int ctrly = random.nextInt(height);

            Path2D path = new Path2D.Double();
            path.moveTo(x1, y1);
            path.curveTo(ctrlx, ctrly, ctrlx, ctrly, x2, y2);
            g2d.draw(path);
        }

        g2d.dispose(); // 释放资源

        // === 6. 输出为 Base64 ===
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(image, "jpeg", outputStream);

        String key = RedisConst.USER_VALIDATE_CODE + ":" + UUID.randomUUID().toString().replace("-", "");
        stringRedisTemplate.opsForValue().set(key, sb.toString(), Duration.ofMinutes(10));

        log.info("生成的验证码为：{}，Redis中的key是：{}", sb, key);

        String base64Img = "data:image/jpeg;base64," + Base64.getEncoder().encodeToString(outputStream.toByteArray());

        Map<String, String> data = new HashMap<>();
        data.put("key", key);  //验证码 redis的key
        data.put("base64Img", base64Img); //图片

        return ResponseEntity.ok(data);
    }

    // 随机颜色（避免太深或太浅）
    private Color getColor() {
        Random random = new Random();
        return new Color(
                30 + random.nextInt(100),  // R: 30~129（避免太亮）
                30 + random.nextInt(100),  // G
                30 + random.nextInt(100)   // B
        );
    }


    /**
     * 手机号发短信
     */
    @GetMapping("/sendSms/{telephone}")
    @Operation(summary = "手机号发短信")
    public ResponseEntity<String> sendSms(@PathVariable String telephone) {
        //随机生成验证码，随机生成4个数字

        // RandomStringUtils 这个是hutool 工具类中的工具
        String code = RandomStringUtils.randomNumeric(4);
        log.info("短信验证码：{}", code);

        //给手机号发送短信，只需要测试一次，如果成功就没有必要每次发送
        //String result = smsUtil.sendSms(telephone, code);
        String result = "OK";
        Assert.error(!result.equals("OK"), BusinessCode.SMS_SEND_ERROR);

        //验证码放在Redis中，10分钟过期
        stringRedisTemplate.opsForValue().set(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone, code, Duration.ofMinutes(10));
        String response = "给手机号：" + maskPhoneNumber(telephone) + "成功发送验证码:" + code;
        return ResponseEntity.ok(response);
    }

    /**
     * 对电话号码进行脱敏处理
     */
    private String maskPhoneNumber(String telephone) {
        // 将第4、5、6、7位替换为 *
        return telephone.substring(0, 3) + "****" + telephone.substring(7);
    }

    /**
     * 短信 验证码登录
     *    1、 获取验证码 判断验证码  成功删除验证码
     *    2、判断是否是第一次登录
     *         注册用户
     *        如果不是第一次就 返回用户
     */
    /**
     * 发短信登录或注册
     */
    @PostMapping("/smsLogin")
    @Operation(summary = "发短信登录或注册")
    public ResponseEntity<?> smsLogin(@RequestBody LoginRequest params, HttpServletRequest request) {
        //获取手机号
        String telephone = params.getTelephone();
        //从Redis中获取验证码
        String smsCode = stringRedisTemplate.opsForValue().get(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone);
        //判断验证码是否过期
        Assert.error(Objects.isNull(smsCode), BusinessCode.INVALID_VERIFICATION);
        //判断验证码是否正确
        String code = params.getCode();
        Assert.error(!code.equals(smsCode), BusinessCode.VERIFICATION_ERROR);

        //删除验证码
        stringRedisTemplate.delete(RedisConst.SHORT_MESSAGE_SERVICE + ":" + telephone);

        //根据手机号查询用户信息
        User user = userService.findByMobile(telephone);
        //如果用户存在，判断状态是否正常
        Assert.error(Objects.nonNull(user) && !user.getState().equals(UserStatus.ACTIVE.getKey()), BusinessCode.USER_FROZEN_ERROR);
        //如果用户不存在，创建新用户
        if (Objects.isNull(user)) {
            //创建默认密码：123
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String encode = encoder.encode("123");
            user = new User();
            user.setMobile(telephone);  //手机号
            user.setPassword(encode);    //加密密码
            user.setAccount(telephone);   //账户名：手机号
            user.setNickname(telephone);
            user.setRealName(telephone);
            user.setState(UserStatus.ACTIVE.getKey());  //状态
            userService.save(user);
        }
        //不为空则登录成功
        return createTokenAndUserVo(request, user);
    }


    //发件人
    @Value("${spring.mail.username}")
    private String from;

    @Autowired
    private JavaMailSender mailSender;

    /**
     *  发送邮件
     * @param address
     * @return
     */
    @GetMapping("/sendEmail")
    @Operation(summary = "发送邮件")
    public ResponseEntity<String> sendEmail(String address) {

        //判断邮箱是否已经注册
        Assert.error(userService.isEmailExist(address), BusinessCode.EMAIL_EXIST_ERROR);
        //发送邮件
        SimpleMailMessage mail = new SimpleMailMessage();
        mail.setFrom(from);//发件箱
        mail.setTo(address);  //收件箱
        mail.setSubject("图书馆：验证码");//邮件主题
        String text = RandomUtil.randomNumbers(4);
        String context = "注册的验证码是：" + text + ", 该验证码15分钟之内有效！";
        mail.setText(context);//生成的验证码

        //为了提升响应速度，开启一个后台线程发送邮件
        new Thread(() -> {
            //发送成功能以后，测试的时候把这句话注释了，以免每次发送邮件，被拉黑
            //mailSender.send(mail);
            //模拟发送邮件
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("验证码{}，发送到{}", text, address);
        }).start();

        //将数据存储在Redis中,时间设置为15分钟，邮箱地址是键
        stringRedisTemplate.opsForValue().set(RedisConst.EMAIL_ADDRESS_PREFIX + ":" + address, text, RedisConst.EMAIL_CODE_EXPIRE, TimeUnit.MINUTES);
        return ResponseEntity.ok("验证码已经发送到" + address + "邮箱，请去查收！验证码:" + text);
    }


    /**
     * 用户注册功能
     */
    @PostMapping("/register")
    @Operation(summary = "用户注册")
    public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest, HttpServletRequest request) {

        //从Redis中获取验证码是否正确
        String redisCode = stringRedisTemplate.opsForValue().get(RedisConst.EMAIL_ADDRESS_PREFIX + ":" + registerRequest.getEmail());
        //判断验证码是否过期 (redis 中是否存在验证码 )
        Assert.error(Objects.isNull(redisCode), BusinessCode.INVALID_VERIFICATION);

        //判断验证码是否正确
        Assert.error(!redisCode.equals(registerRequest.getCode()), BusinessCode.VERIFICATION_ERROR);

        //判断用户名是否重复
        Assert.error(userService.isAccountExist(registerRequest.getAccount()), BusinessCode.USER_IS_EXIST);

        //判断手机是否重复
        Assert.error(userService.isMobileExist(registerRequest.getMobile()), BusinessCode.MOBILE_IS_EXIST);

        //创建用户对象
        User user = new User();
        //将注册信息复制到user对象中
        BeanUtils.copyProperties(registerRequest, user);

        //调用业务层注册
        User newUser = userService.register(user);

        //默认已经登录
        return createTokenAndUserVo(request, newUser);

    }
}
